#ifdef WX_PRECOMP //
#include "wx_pch.h"
#endif

#ifdef __BORLANDC__
#pragma hdrstop
#endif //__BORLANDC__

#include "sc/setup.h"
#include "sc/interface.h"
#include "sc/xml.h"

#include <set>
#include <algorithm>

#include "boost/foreach.hpp"

using namespace sc;

/**
* Setup Element constructor
*
* @param name    name of the Element
* @param content content of the Element
*/
setup::Element::Element(const wxString& name, const wxString& content)
: m_sName(name)
, m_sContent(content)
{
}

/**
* Setup Element constructor
*
* @param name     name of the Element
* @param content  content of the Element
* @param iterCfg  iterator on the configuration of this Element
*/
setup::Element::Element(const wxString& name, const wxString& content, cfg::TElements::const_iterator iterCfg)
: m_sName(name)
, m_sContent(content)
, m_config(iterCfg)
{
    // Create all attributes
    BOOST_FOREACH (const cfg::Attribute& rcConfAttribute, m_config->second.GetAttributes())
    {
        m_attributes.push_back( Attribute(wxEmptyString, rcConfAttribute) );
    }
    AddUniqueChildren(true);
}

/**
* Setup Element copy constructor
*
* @param rcElement  source Element to copy
*/
setup::Element::Element(const Element& rcElement)
{
    *this = rcElement;
}

/**
* Setup Element copy operator
*
* @param rcElement  source Element to copy
*
* @return this
*/
setup::Element& setup::Element::operator=(const Element& rcElement)
{
    if (&rcElement != this)
    {
        m_sName      = rcElement.m_sName;
        m_sContent   = rcElement.m_sContent;
        m_attributes = rcElement.m_attributes;
        m_children   = rcElement.m_children;
        m_config     = rcElement.m_config;

        AddUniqueChildren(true);
    }
    return *this;
}

/**
* Setup Destructor
*/
setup::Element::~Element()
{
}

/**
* Add a child to the element. The child is added only if his name is correct, and
*   if it doesn't appear as a child in case of OPTION or UNIQUE type
*
* @param rcElement   Element to add
* @param rIterChild [out] iterator on the added element
*
* @return error
*/
int setup::Element::AddChild(const Element& rcElement, TElements::iterator& rIterChild)
{
	int error = error::CHILD_INVALID;

	const cfg::TChildren& availableChildren = m_config->second.GetChildren();
	cfg::TChildren::const_iterator iterFound = std::find_if( availableChildren.begin()
														   , availableChildren.end()
														   , PredFindConfigChildByName(rcElement.m_sName)
														   );
	if (iterFound != availableChildren.end()/* && rcElement.IsValid()*/)
	{
        // we can add the child
        rIterChild = m_children.insert(m_children.end(), rcElement);

        error = error::NONE;
	}
	return error;
}

/**
* Add a child to the element. The child is added only if his name is correct, and
*   if it doesn't appear as a child in case of OPTION or UNIQUE type.
*
* @param rcElement   Element to add
*
* @return error
*/
int setup::Element::AddChild(const Element& rcElement)
{
    TElements::iterator iterChild;
    return AddChild(rcElement, iterChild);
}

/**
* Add all the UNIQUE children of the Element, according to the config
*
* @param bCanOverwrite true if can overwrite existing children
*
* @return error
*/
int setup::Element::AddUniqueChildren(bool bCanOverwrite)
{
    int error = error::NONE;

    // Add the UNIQUE elements
    BOOST_FOREACH (const cfg::TChildren::value_type& iterConf, m_config->second.GetChildren())
    {
        const cfg::Element& availableChild = iterConf->second;
        if ( availableChild.GetType() == cfg::Element::UNIQUE )
        {
            Element child(availableChild.GetName(), wxEmptyString, iterConf);
            if (bCanOverwrite)
            {
                TElements::iterator iterChild = std::find_if( m_children.begin()
                                                            , m_children.end()
                                                            , PredFindChildByName(availableChild.GetName())
                                                            );
                if (iterChild == m_children.end())
                {
                    // The child doesn't exist
                    error = AddChild(child);
                }
            }
            else
            {
                error = AddChild(child);
            }
        }
        _foreach_continue = !error;
    }
    return error;
}

/**
* Load the Element with the xml attributes and children
*
* @param xmlNode
*
* @return loading error
*/
int setup::Element::Load(xmlNodePtr xmlNode)
{
    int error = error::NONE;

    // Get all attributes
    struct _xmlAttr* attrib = xmlNode->properties;
    while (attrib && !error)
    {
        wxString sAttrib = xml::ToWx(attrib->name);
        TAttributes::iterator iterFound = std::find_if( m_attributes.begin()
                                                      , m_attributes.end()
                                                      , setup::PredFindAttributeByName(sAttrib)
                                                      );
        if (iterFound != m_attributes.end())
        {
            iterFound->m_sValue = xml::ToWx(xmlGetProp(xmlNode, attrib->name));
        }
        else
        {
            error = error::SHEMA_INVALID;
        }
        attrib = attrib->next;
    }

    // Get the content (if any) and all children
    xmlNodePtr child = xmlNode->children;
    while (child && !error)
    {
        if (child->type == XML_ELEMENT_NODE)
        {
            wxString sChild = xml::ToWx(child->name);

            const cfg::TChildren& availableChildren = m_config->second.GetChildren();
            cfg::TChildren::const_iterator iterChildCfg = std::find_if( availableChildren.begin()
                                                                      , availableChildren.end()
                                                                      , PredFindConfigChildByName(sChild)
                                                                      );
            if (iterChildCfg != availableChildren.end())
            {
                Element element(sChild, wxEmptyString, *iterChildCfg);
                error = element.Load(child);
                if (!error)
                {
                    if ((*iterChildCfg)->second.GetType() == cfg::Element::UNIQUE)
                    {
                        TElements::iterator iterUniqueChild = std::find_if( m_children.begin()
                                                                          , m_children.end()
                                                                          , PredFindChildByName(sChild)
                                                                          );
                        assert(iterUniqueChild != m_children.end());

                        *iterUniqueChild = element;
                    }
                    else
                    {
                        m_children.push_back(element);
                    }
                }
            }
            else
            {
                error = error::CHILD_INVALID;
            }
        }
        child = child->next;
    }
    return error;
}

/**
* Verify if an Element is valid, ie: it is correct in accordance with his config.
*
* @return true if the element is valid
*/
bool setup::Element::IsValid(void) const
{
	bool bValid = (m_sName == m_config->first);

	// verify the attributes
	BOOST_FOREACH (const TAttributes::value_type& rcAttribute, m_attributes)
	{
	    if (rcAttribute.m_pcConf->IsOptional())
	    {
	        if (!rcAttribute.m_sValue.empty())
	        {
	            // Validate non-empty optional attribute
                bValid &= rcAttribute.m_pcConf->Validate(rcAttribute.m_sValue);
	        }
	    }
	    else
	    {
            bValid &= rcAttribute.m_pcConf->Validate(rcAttribute.m_sValue);
	    }
	}

    // Verify the existing children
	std::set<wxString> childPresent;
	BOOST_FOREACH (const Element& rcChild, m_children)
	{
		cfg::Element::Type type = rcChild.GetConfigType();
		if ((type == cfg::Element::OPTION) || (type == cfg::Element::UNIQUE))
		{
			// Test if there is no more than one element
			bValid &= childPresent.insert(rcChild.m_sName).second;
		}
		bValid &= rcChild.IsValid();
	}

	// Verify if all the UNIQUE children exist
	BOOST_FOREACH (cfg::TElements::const_iterator iterChildCfg, m_config->second.GetChildren())
	{
	    if (iterChildCfg->second.GetType() == cfg::Element::UNIQUE)
	    {
	        bValid &= childPresent.find(iterChildCfg->second.GetName()) != childPresent.end();
	    }
	}
	if (!bValid)
	{
        wxLogMessage(wxT("setup::Element[%s]::IsValid() => INVALID !"), m_sName.c_str());
	}
	return bValid;
}

/**
* Verify the child is available to be added to the current element
*
* @param sChildName  name of the child
*
* @return true if can add the child to the current element
*/
bool setup::Element::IsChildAvailable(const wxString& sChildName) const
{
	bool bAvailable = false;

	BOOST_FOREACH (const cfg::TChildren::value_type& rcChildValue, m_config->second.GetChildren())
	{
		const cfg::Element& rcChildConf = rcChildValue->second;
		if (rcChildConf.GetName() == sChildName)
		{
			// The children is available
			bAvailable = true;

			if ((rcChildConf.GetType() == cfg::Element::UNIQUE) || (rcChildConf.GetType() == cfg::Element::OPTION))
			{
				TElements::const_iterator iterFound = std::find_if( m_children.begin()
					                                              , m_children.end()
                                                                  , PredFindChildByName(sChildName)
																  );
				if (iterFound != m_children.end())
				{
					// The children already exist, so it is not available
					bAvailable = false;
				}
			}
			_foreach_continue = false;
		}
	}
	return bAvailable;
}

/**
* Get all available children of an element
*
* @param rListChild [out]
*/
void setup::Element::GetAvailableChildren(std::set<wxString>& rListChild) const
{
    BOOST_FOREACH (const cfg::TChildren::value_type& iter, m_config->second.GetChildren())
    {
        if (IsChildAvailable(iter->first))
        {
            rListChild.insert(iter->first);
        }
    }
}

/**
* Delete an element
*
* @param pcElement Element to delete
*
* @return delete error
*/
int setup::Element::Delete(const Element* pcElement)
{
    wxLogMessage(wxT("setup::Element::Delete(%s) in %s"), pcElement->GetName().c_str(), m_sName.c_str());

	int error = error::ELEMENT_DOESNT_EXIST;

	TElements::iterator iter;
	for (iter = m_children.begin(); iter != m_children.end() && (error == error::ELEMENT_DOESNT_EXIST); ++iter)
	{
		if (&(*iter) == pcElement)
		{
			// We found the element
			m_children.erase(iter);
			error = error::NONE;
		}
		else
		{
			error = iter->Delete(pcElement);
		}
	}
	return error;
}

/**
* Save an element to an xml document
*
* @param xmlWriter  xml writer, ie : stream of the file
*
* @return saving error
*/
int setup::Element::Save(xmlTextWriterPtr xmlWriter) const
{
	int error = error::CANT_SAVE_FILE;

	// Add the xml node with the name of the element
	if (xmlTextWriterStartElement(xmlWriter, (const xmlChar*) m_sName.utf8_str().data()) >= 0)
	{
		error = error::NONE;

		// Add the attributes
        BOOST_FOREACH (const TAttributes::value_type& rcAttribute, m_attributes)
		{
		    if (!rcAttribute.m_pcConf->IsOptional() || !rcAttribute.m_sValue.empty())
		    {
                if ( xmlTextWriterWriteAttribute( xmlWriter
                                                , (const xmlChar*) rcAttribute.GetName().utf8_str().data()
                                                , (const xmlChar*) rcAttribute.m_sValue.utf8_str().data()
                                                ) < 0 )
                {
                    error = error::CANT_SAVE_FILE;
                }
		    }
		}

		// Add the children
        BOOST_FOREACH (const Element& rcChild, m_children)
		{
			error = rcChild.Save(xmlWriter);
		}

		xmlTextWriterEndElement(xmlWriter);
	}
	return error;
}


/**
* Setup constructor
*/
Setup::Setup()
: m_pRoot(NULL)
{}

/**
* Setup destructor
*/
Setup::~Setup()
{
	Clear();
}

/**
* Clear the setup.
*  Remove all information about Setup
*/
void Setup::Clear(void)
{
    m_config.Clear();
    if (m_pRoot)
    {
        delete m_pRoot;
        m_pRoot = NULL;
    }
}

/**
* Load a config file, ie: a rng file which describe the capacity of of the setup
*
* @param sConfigFilename File name of the config file (RNG file)
*
* @return error
*/
int Setup::LoadConfig(const wxString& sConfigFilename)
{
    wxLogMessage(wxT("Setup::LoadConfig(%s)"), sConfigFilename.c_str());

	Clear();

	// load config
	int error = m_config.LoadRng(sConfigFilename);
	if (!error)
	{
		// Add the root to the setup, in accordance with the config loaded
		wxString sRoot;
		error = m_config.GetRoot(sRoot);
		if (!error)
		{
			m_pRoot = new setup::Element(sRoot, wxEmptyString, m_config.GetElements().find(sRoot));
		}
	}
    wxLogMessage(wxT("Setup::LoadConfig(%s) return %d"), sConfigFilename.c_str(), error);
	return error;
}

/**
* Load the setup
*
* @param sFilename
*
* @return loading error
*/
int Setup::Load(const wxString& sFilename)
{
    wxLogMessage(wxT("Setup::Load(%s)"), sFilename.c_str());

    xmlDocPtr xmlDoc = xmlReadFile(sFilename.utf8_str(), NULL, 0);
    if (xmlDoc == NULL)
        return error::CANT_OPEN_FILE;

    int error = error::UNKNOWN;
    xmlNodePtr root = xmlDocGetRootElement(xmlDoc);
    if (root && root->name)
    {
        delete m_pRoot;

        // Load the root Element
        wxString sRoot = xml::ToWx(root->name);
        m_pRoot = new setup::Element(sRoot, wxEmptyString, m_config.GetElements().find(sRoot));
        if (m_pRoot)
        {
            // Load the element
            error = m_pRoot->Load(root);
        }
   }
   xmlFreeDoc(xmlDoc);

    return error;
}

/**
* Delete an element
*
* @param pcElement Element to delete
*
* @return delete error
*/
int Setup::DeleteElement(const setup::Element* pcElement)
{
	if (m_pRoot == NULL)
		return error::RNG_NOT_LOADED;

	return m_pRoot->Delete(pcElement);
}

/**
* Save the setup as a xml file, with the grammar defined corresponding to the config (RNG) file
*
* @param sFilename  file name of the file where the setup will be saved
*
* @return saving error
*/
int Setup::Save(const wxString& sFilename) const
{
    wxLogMessage(wxT("Setup::Save(%s)"), sFilename.c_str());

	if (m_pRoot == NULL)
		return error::RNG_NOT_LOADED;

	int error = error::CANT_SAVE_FILE;

	xmlTextWriterPtr xmlWriter = xmlNewTextWriterFilename(sFilename.utf8_str(), 0);
	if (xmlWriter)
	{
		if ( !xmlTextWriterSetIndent(xmlWriter, 1)
		  && !xmlTextWriterSetIndentString(xmlWriter, (const xmlChar*) "  ")
		  && (xmlTextWriterStartDocument(xmlWriter, NULL, "UTF-8", NULL) >= 0)
		   )
		{
			error = m_pRoot->Save(xmlWriter);
			if (!error)
			{
				if (xmlTextWriterEndDocument(xmlWriter) < 0)
				{
					error = error::CANT_SAVE_FILE;
				}
			}
		}
		xmlFreeTextWriter(xmlWriter);
	}
	return error;
}
